/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
/* JOrbis
 * Copyright (C) 2000 ymnk, JCraft,Inc.
 *  
 * Written by: 2000 ymnk<ymnk@jcraft.com>
 *   
 * Many thanks to 
 *   Monty <monty@xiph.org> and 
 *   The XIPHOPHORUS Company http://www.xiph.org/ .
 * JOrbis has been based on their awesome works, Vorbis codec.
 *   
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public License
 * as published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Library General Public License for more details.
 * 
 * You should have received a copy of the GNU Library General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

package com.jcraft.jorbis;

import com.jcraft.jogg.*;

public class Info {
	private static final int OV_EBADPACKET = -136;
	private static final int OV_ENOTAUDIO = -135;

	private static byte[] _vorbis = "vorbis".getBytes();
	private static final int VI_TIMEB = 1;
	// private static final int VI_FLOORB=1;
	private static final int VI_FLOORB = 2;
	// private static final int VI_RESB=1;
	private static final int VI_RESB = 3;
	private static final int VI_MAPB = 1;
	private static final int VI_WINDOWB = 1;

	public int version;
	public int channels;
	public int rate;

	// The below bitrate declarations are *hints*.
	// Combinations of the three values carry the following implications:
	//
	// all three set to the same value:
	// implies a fixed rate bitstream
	// only nominal set:
	// implies a VBR stream that averages the nominal bitrate. No hard
	// upper/lower limit
	// upper and or lower set:
	// implies a VBR bitstream that obeys the bitrate limits. nominal
	// may also be set to give a nominal rate.
	// none set:
	// the coder does not care to speculate.

	int bitrate_upper;
	int bitrate_nominal;
	int bitrate_lower;

	// Vorbis supports only short and long blocks, but allows the
	// encoder to choose the sizes

	int[] blocksizes = new int[2];

	// modes are the primary means of supporting on-the-fly different
	// blocksizes, different channel mappings (LR or mid-side),
	// different residue backends, etc. Each mode consists of a
	// blocksize flag and a mapping (along with the mapping setup

	int modes;
	int maps;
	int times;
	int floors;
	int residues;
	int books;
	int psys; // encode only

	InfoMode[] mode_param = null;

	int[] map_type = null;
	Object[] map_param = null;

	int[] time_type = null;
	Object[] time_param = null;

	int[] floor_type = null;
	Object[] floor_param = null;

	int[] residue_type = null;
	Object[] residue_param = null;

	StaticCodeBook[] book_param = null;

	PsyInfo[] psy_param = new PsyInfo[64]; // encode only

	// for block long/sort tuning; encode only
	int envelopesa;
	float preecho_thresh;
	float preecho_clamp;

	// used by synthesis, which has a full, alloced vi
	public void init() {
		rate = 0;
	}

	public void clear() {
		for (int i = 0; i < modes; i++) {
			mode_param[i] = null;
		}
		mode_param = null;

		for (int i = 0; i < maps; i++) { // unpack does the range checking
			FuncMapping.mapping_P[map_type[i]].free_info(map_param[i]);
		}
		map_param = null;

		for (int i = 0; i < times; i++) { // unpack does the range checking
			FuncTime.time_P[time_type[i]].free_info(time_param[i]);
		}
		time_param = null;

		for (int i = 0; i < floors; i++) { // unpack does the range checking
			FuncFloor.floor_P[floor_type[i]].free_info(floor_param[i]);
		}
		floor_param = null;

		for (int i = 0; i < residues; i++) { // unpack does the range checking
			FuncResidue.residue_P[residue_type[i]].free_info(residue_param[i]);
		}
		residue_param = null;

		// the static codebooks *are* freed if you call info_clear, because
		// decode side does alloc a 'static' codebook. Calling clear on the
		// full codebook does not clear the static codebook (that's our
		// responsibility)
		for (int i = 0; i < books; i++) {
			// just in case the decoder pre-cleared to save space
			if (book_param[i] != null) {
				book_param[i].clear();
				book_param[i] = null;
			}
		}
		// if(vi->book_param)free(vi->book_param);
		book_param = null;

		for (int i = 0; i < psys; i++) {
			psy_param[i].free();
		}

	}

	// Header packing/unpacking
	int unpack_info(Buffer opb) {
		version = opb.read(32);
		if (version != 0)
			return (-1);

		channels = opb.read(8);
		rate = opb.read(32);

		bitrate_upper = opb.read(32);
		bitrate_nominal = opb.read(32);
		bitrate_lower = opb.read(32);

		blocksizes[0] = 1 << opb.read(4);
		blocksizes[1] = 1 << opb.read(4);

		if ((rate < 1) || (channels < 1) || (blocksizes[0] < 8)
				|| (blocksizes[1] < blocksizes[0]) || (opb.read(1) != 1)) {
			clear();
			return (-1);
		}
		return (0);
	}

	// all of the real encoding details are here. The modes, books,
	// everything
	int unpack_books(Buffer opb) {

		books = opb.read(8) + 1;

		if (book_param == null || book_param.length != books)
			book_param = new StaticCodeBook[books];
		for (int i = 0; i < books; i++) {
			book_param[i] = new StaticCodeBook();
			if (book_param[i].unpack(opb) != 0) {
				clear();
				return (-1);
			}
		}

		// time backend settings
		times = opb.read(6) + 1;
		if (time_type == null || time_type.length != times)
			time_type = new int[times];
		if (time_param == null || time_param.length != times)
			time_param = new Object[times];
		for (int i = 0; i < times; i++) {
			time_type[i] = opb.read(16);
			if (time_type[i] < 0 || time_type[i] >= VI_TIMEB) {
				clear();
				return (-1);
			}
			time_param[i] = FuncTime.time_P[time_type[i]].unpack(this, opb);
			if (time_param[i] == null) {
				clear();
				return (-1);
			}
		}

		// floor backend settings
		floors = opb.read(6) + 1;
		if (floor_type == null || floor_type.length != floors)
			floor_type = new int[floors];
		if (floor_param == null || floor_param.length != floors)
			floor_param = new Object[floors];

		for (int i = 0; i < floors; i++) {
			floor_type[i] = opb.read(16);
			if (floor_type[i] < 0 || floor_type[i] >= VI_FLOORB) {
				clear();
				return (-1);
			}

			floor_param[i] = FuncFloor.floor_P[floor_type[i]].unpack(this, opb);
			if (floor_param[i] == null) {
				clear();
				return (-1);
			}
		}

		// residue backend settings
		residues = opb.read(6) + 1;

		if (residue_type == null || residue_type.length != residues)
			residue_type = new int[residues];

		if (residue_param == null || residue_param.length != residues)
			residue_param = new Object[residues];

		for (int i = 0; i < residues; i++) {
			residue_type[i] = opb.read(16);
			if (residue_type[i] < 0 || residue_type[i] >= VI_RESB) {
				clear();
				return (-1);
			}
			residue_param[i] = FuncResidue.residue_P[residue_type[i]].unpack(
					this, opb);
			if (residue_param[i] == null) {
				clear();
				return (-1);
			}
		}

		// map backend settings
		maps = opb.read(6) + 1;
		if (map_type == null || map_type.length != maps)
			map_type = new int[maps];
		if (map_param == null || map_param.length != maps)
			map_param = new Object[maps];
		for (int i = 0; i < maps; i++) {
			map_type[i] = opb.read(16);
			if (map_type[i] < 0 || map_type[i] >= VI_MAPB) {
				clear();
				return (-1);
			}
			map_param[i] = FuncMapping.mapping_P[map_type[i]].unpack(this, opb);
			if (map_param[i] == null) {
				clear();
				return (-1);
			}
		}

		// mode settings
		modes = opb.read(6) + 1;
		if (mode_param == null || mode_param.length != modes)
			mode_param = new InfoMode[modes];
		for (int i = 0; i < modes; i++) {
			mode_param[i] = new InfoMode();
			mode_param[i].blockflag = opb.read(1);
			mode_param[i].windowtype = opb.read(16);
			mode_param[i].transformtype = opb.read(16);
			mode_param[i].mapping = opb.read(8);

			if ((mode_param[i].windowtype >= VI_WINDOWB)
					|| (mode_param[i].transformtype >= VI_WINDOWB)
					|| (mode_param[i].mapping >= maps)) {
				clear();
				return (-1);
			}
		}

		if (opb.read(1) != 1) {
			clear();
			return (-1);
		}

		return (0);
	}

	// The Vorbis header is in three packets; the initial small packet in
	// the first page that identifies basic parameters, a second packet
	// with bitstream comments and a third packet that holds the
	// codebook.

	public int synthesis_headerin(Comment vc, Packet op) {
		Buffer opb = new Buffer();

		if (op != null) {
			opb.readinit(op.packet_base, op.packet, op.bytes);

			// Which of the three types of header is this?
			// Also verify header-ness, vorbis
			{
				byte[] buffer = new byte[6];
				int packtype = opb.read(8);
				opb.read(buffer, 6);
				if (buffer[0] != 'v' || buffer[1] != 'o' || buffer[2] != 'r'
						|| buffer[3] != 'b' || buffer[4] != 'i'
						|| buffer[5] != 's') {
					// not a vorbis header
					return (-1);
				}
				switch (packtype) {
				case 0x01: // least significant *bit* is read first
					if (op.b_o_s == 0) {
						// Not the initial packet
						return (-1);
					}
					if (rate != 0) {
						// previously initialized info header
						return (-1);
					}
					return (unpack_info(opb));
				case 0x03: // least significant *bit* is read first
					if (rate == 0) {
						// um... we didn't get the initial header
						return (-1);
					}
					return (vc.unpack(opb));
				case 0x05: // least significant *bit* is read first
					if (rate == 0 || vc.vendor == null) {
						// um... we didn;t get the initial header or comments
						// yet
						return (-1);
					}
					return (unpack_books(opb));
				default:
					// Not a valid vorbis header type
					// return(-1);
					break;
				}
			}
		}
		return (-1);
	}

	// pack side
	int pack_info(Buffer opb) {
		// preamble
		opb.write(0x01, 8);
		opb.write(_vorbis);

		// basic information about the stream
		opb.write(0x00, 32);
		opb.write(channels, 8);
		opb.write(rate, 32);

		opb.write(bitrate_upper, 32);
		opb.write(bitrate_nominal, 32);
		opb.write(bitrate_lower, 32);

		opb.write(Util.ilog2(blocksizes[0]), 4);
		opb.write(Util.ilog2(blocksizes[1]), 4);
		opb.write(1, 1);
		return (0);
	}

	int pack_books(Buffer opb) {
		opb.write(0x05, 8);
		opb.write(_vorbis);

		// books
		opb.write(books - 1, 8);
		for (int i = 0; i < books; i++) {
			if (book_param[i].pack(opb) != 0) {
				// goto err_out;
				return (-1);
			}
		}

		// times
		opb.write(times - 1, 6);
		for (int i = 0; i < times; i++) {
			opb.write(time_type[i], 16);
			FuncTime.time_P[time_type[i]].pack(this.time_param[i], opb);
		}

		// floors
		opb.write(floors - 1, 6);
		for (int i = 0; i < floors; i++) {
			opb.write(floor_type[i], 16);
			FuncFloor.floor_P[floor_type[i]].pack(floor_param[i], opb);
		}

		// residues
		opb.write(residues - 1, 6);
		for (int i = 0; i < residues; i++) {
			opb.write(residue_type[i], 16);
			FuncResidue.residue_P[residue_type[i]].pack(residue_param[i], opb);
		}

		// maps
		opb.write(maps - 1, 6);
		for (int i = 0; i < maps; i++) {
			opb.write(map_type[i], 16);
			FuncMapping.mapping_P[map_type[i]].pack(this, map_param[i], opb);
		}

		// modes
		opb.write(modes - 1, 6);
		for (int i = 0; i < modes; i++) {
			opb.write(mode_param[i].blockflag, 1);
			opb.write(mode_param[i].windowtype, 16);
			opb.write(mode_param[i].transformtype, 16);
			opb.write(mode_param[i].mapping, 8);
		}
		opb.write(1, 1);
		return (0);
	}

	public int blocksize(Packet op) {
		// codec_setup_info
		Buffer opb = new Buffer();

		int mode;

		opb.readinit(op.packet_base, op.packet, op.bytes);

		/* Check the packet type */
		if (opb.read(1) != 0) {
			/* Oops. This is not an audio data packet */
			return (OV_ENOTAUDIO);
		}
		{
			int modebits = 0;
			int v = modes;
			while (v > 1) {
				modebits++;
				v >>>= 1;
			}

			/* read our mode and pre/post windowsize */
			mode = opb.read(modebits);
		}
		if (mode == -1)
			return (OV_EBADPACKET);
		return (blocksizes[mode_param[mode].blockflag]);
	}

	public String toString() {
		return "version:" + new Integer(version) + ", channels:"
				+ new Integer(channels) + ", rate:" + new Integer(rate)
				+ ", bitrate:" + new Integer(bitrate_upper) + ","
				+ new Integer(bitrate_nominal) + ","
				+ new Integer(bitrate_lower);
	}
}
